using System;
using System.Collections.Generic;
using System.IO;

namespace Gloom
{
	public static class UnmanagedGLTypeTranslator
	{
		// Information for this based on OpenTK's csharp.tm -- thanks, guys.
		// (Not sure about: all the IntPtr ones right now. :(  Needs testing!)
		private static Dictionary<string, string> typeMap = new Dictionary<string, string>()
		{ { "GLsizei", "Int32" },
			{ "GLsizeiptr", "IntPtr" },
			{ "GLintptr", "IntPtr" },
			{ "GLenum", "uint" },  // Note enums are treated specially in the translation process!
			{ "GLboolean", "byte" },  // false = 0, true = 1
			{ "GLbitfield", "UInt32" },
			{ "GLchar", "byte" }, // OpenTK says Char, but c# chars are 16-bit!! (GLchar = C char = 8-bits.)
			{ "GLbyte", "SByte" }, // lol signed bytes
			{ "GLubyte", "byte" },
			{ "GLshort", "Int16" },
			{ "GLushort", "UInt16" },
			{ "GLint", "Int32" },
			{ "GLuint", "UInt32" },
			{ "GLfloat", "float" },
			{ "GLclampf", "float" }, // TODO: Make note in docstrings that valid range is 0.0 to 1.0??
			{ "GLdouble", "double" },
			{ "GLclampd", "double" },
			{ "GLstring", "IntPtr" },
			//{ "const GLUbyte *", "IntPtr" }, // i.e. GLstring again
			{ "GLsizeiptrARB", "IntPtr" },
			{ "GLintptrARB", "IntPtr" },
			{ "GLhandleARB", "UInt32" },
			{ "GLcharARB", "byte" },
			{ "GLint64EXT", "Int64" },
			{ "GLuint64EXT", "UInt64" },
			{ "GLint64", "Int64" },
			{ "GLuint64", "UInt64" },
			{ "GLsync", "IntPtr" },
			//{ "void", "void" },
			{ "GLvoid", "void" } };
		
		// There is one block of functions in the whole GL spec atm that use the half type, and
		//  it uses GLhalfNV -- it's an NVidia extension that works only on NVidia cards.
		// Conclusion: Let's not bother trying to implement half floats right now.
		private static List<string> blacklistedTypes = new List<string>() { "GLhalfARB", "GLhalfNV" };
		
		public static bool TryTranslate(string rawType, string inOrOut, string valueOrArray, out string outType, StreamWriter infoLog, StreamWriter errorLog)
		{
			infoLog.WriteLine("UnmanagedGLTypeTranslator.TryTranslate " + rawType + " " + inOrOut + " " + valueOrArray);
			
			bool isVoid;
			bool firstOk = tryFirstPass(rawType, valueOrArray, inOrOut, out outType, out isVoid, infoLog, errorLog);
			bool secondOk = false;
			
			if (firstOk)
				secondOk = tryTranslatePrimitiveType(outType, out outType, infoLog, errorLog);
			
			if (firstOk && secondOk)
			{
				if (valueOrArray == "array" && inOrOut == "out" && !isVoid)
				{
					infoLog.WriteLine("UnmanagedGLTypeTranslator.TryTranslate: out array, so doing out *.");
					//outType = "ref " + outType + "[]";
					outType = "out " + outType + "*";
				}
				else if (inOrOut == "out")
				{
					infoLog.WriteLine("UnmanagedGLTypeTranslator.TryTranslate: it's an out type, so adding an out keyword to the front of the produced type.");
					outType = "out " + outType;
				}
				return true;
			}
			else
				return false;
		}
		
		private static bool tryFirstPass(string rawType, string valueOrArray, string inOrOut, out string glPrimTypeOut, out bool isVoid, StreamWriter infoLog, StreamWriter errorLog)
		{
			glPrimTypeOut = "FAIL";
			
			isVoid = false;
			
			/*if (valueOrArray == "array" && (inOrOut == "in" || rawType == "void" || rawType == "Void" || rawType == "GLvoid"))
			{
				
			}
			else // if (valueOrArray == "value")
			{*/
				infoLog.WriteLine("UnmanagedGLTypeTranslator.tryFirstPass " + rawType); // + " value");
				bool b = GLTypePrimitiveMapper.TryMap(rawType, out glPrimTypeOut, infoLog, errorLog);
				
				if (valueOrArray == "array" && (inOrOut == "in" || rawType == "void" || rawType == "Void" || rawType == "GLvoid" || (b && glPrimTypeOut == "GLvoid")))
				{
					infoLog.WriteLine("UnmanagedGLTypeTranslator.tryFirstPass " + rawType + " array, so conv. -> IntPtr/GLvoid (managed needs to do fix-up)");
					glPrimTypeOut = "GLintptr"; // ...so that we end up getting IntPtr
					isVoid = true;
					return true;
				}
				
				// Is it a pointer? (e.g. `const GLubyte *')
				if (b && glPrimTypeOut.EndsWith("*"))
				{
					infoLog.WriteLine("UnmanagedGLTypeTranslator.tryFirstPass: GLTypePrimitiveMapper.TryMap() converted `" + rawType + "' to `" + glPrimTypeOut + "' (pointer!), so we'll just put this as an IntPtr/GLintptr.");
					glPrimTypeOut = "GLintptr";
					isVoid = false;
				}
				
				return b;
			//}

			//}
			//else
			//	return false;
				//throw new Exception("Parse failure: Expected 'value' or 'array', got '" + valueOrArray + "'");
		}
		
		private static bool tryTranslatePrimitiveType(string glPrimitiveType, out string dotNetType, StreamWriter infoLog, StreamWriter errorLog)
		{
			dotNetType = "";
			
			if (blacklistedTypes.Contains(glPrimitiveType))
			{
				errorLog.WriteLine("Primitive type `" + glPrimitiveType + "' is blacklisted in Gloom/UnmanagedGLTypeTranslator.cs.");
				infoLog.WriteLine("Primitive type `" + glPrimitiveType + "' is blacklisted in Gloom/UnmanagedGLTypeTranslator.cs.");
				return false;
			}
			else if (typeMap.ContainsKey(glPrimitiveType))
			{
				dotNetType = typeMap[glPrimitiveType];
				return true;
			}
			else
			{
				errorLog.WriteLine("There is no OpenGL>CSharp primitive type mapping for this type (`" + glPrimitiveType + "') in Gloom/SpecLoad/PrimitiveTypeMapper.cs.");
				infoLog.WriteLine("There is no OpenGL>CSharp primitive type mapping for this type (`" + glPrimitiveType + "') in Gloom/SpecLoad/PrimitiveTypeMapper.cs.");
				return false;
			}
		}
	}
}

